﻿namespace BloodlustFury.DataMiner.Business
{
	#region "Using Statements"

	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Text;
	using System.IO;
	using FileHelpers;

	#endregion

	public static class DataEngine
	{
		#region "Public Statis Events"
		
		public static EventHandler<EventArgs> OnCaseLibraryLoaded;
		public static EventHandler<EventArgs> OnTestModulesLoaded;
		public static EventHandler<EventArgs> OnSolutionsLoaded;
		public static EventHandler<EventArgs> OnStatisticsGenerated;
		public static EventHandler<EventArgs> OnPredictionsGenerated;

		#endregion

		#region "Public Static Variables"

		public static ModuleList<TestModule> TestModules;
		public static ModuleList<Module> CaseLibrary;

		public static Boolean TestModulesLoaded = false;
		public static Boolean CaseLibraryLoaded = false;
		public static Boolean SolutionsLoaded = false;
		public static Boolean StatisticsGenerated = false;
		public static Boolean PredictionsGenerated = false;

		#endregion

		#region "Load Methods"
		
		public static void LoadCaseLibrary(String filePath)
		{
			CaseLibrary = new ModuleList<Module>();
			CaseLibrary.LoadModules(filePath);

			DataEngine.CaseLibraryLoaded = true;

			EventHandler<EventArgs> myEvent = OnCaseLibraryLoaded;

			if (myEvent != null)
			{
				myEvent(null, new EventArgs());
			}
		}

		public static void LoadTestModules(String filePath)
		{
			TestModules = new ModuleList<TestModule>();
			TestModules.LoadModules(filePath);

			DataEngine.TestModulesLoaded = true;
			DataEngine.SolutionsLoaded = false;

			EventHandler<EventArgs> myEvent = OnTestModulesLoaded;

			if (myEvent != null)
			{
				myEvent(null, new EventArgs());
			}
		}

		public static void LoadSolutionData(String filePath)
		{
			int x = 0;

			if (TestModules == null)
			{
				throw new InvalidOperationException("You must first load test modules.");
			}

			FileHelperEngine engine = new FileHelperEngine(typeof(SolutionDataFileDataRow));
			SolutionDataFileDataRow[] data = engine.ReadFile(filePath) as SolutionDataFileDataRow[];

			if (data.Count() != TestModules.ActualItems.Count)
			{
				throw new InvalidDataException("The solution file does not contain data for each test module.");
			}

			foreach (SolutionDataFileDataRow row in data)
			{
				TestModules.ActualItems[x].Y = row.Y;

				x++;
			}

			DataEngine.SolutionsLoaded = true;

			EventHandler<EventArgs> myEvent = OnSolutionsLoaded;

			if (myEvent != null)
			{
				myEvent(null, new EventArgs());
			}
		}

		#endregion

		#region "Prediction Methods"

		public static void FindNearestNeighbor(int NumOfNearestNeighbors)
		{
			double[] MinDistance = new double[NumOfNearestNeighbors];
			double[] MinDistanceY = new double[NumOfNearestNeighbors];
			double newDistance = 0;

			for (int h = 0; h < TestModules.Items.Count; h++)
			{
				//Throw the first <NumOfNearestNeighbors> into the MinEuclideanDistance array
				for (int i = 0; i < NumOfNearestNeighbors; i++)
				{
					MinDistance[i] = Math.Pow(TestModules.Items[h].X1 - CaseLibrary.Items[i].X1, 2) + Math.Pow(TestModules.Items[h].X2 - CaseLibrary.Items[i].X2, 2)
							+ Math.Pow(TestModules.Items[h].X3 - CaseLibrary.Items[i].X3, 2) + Math.Pow(TestModules.Items[h].X4 - CaseLibrary.Items[i].X4, 2)
							+ Math.Pow(TestModules.Items[h].X5 - CaseLibrary.Items[i].X5, 2) + Math.Pow(TestModules.Items[h].X6 - CaseLibrary.Items[i].X6, 2)
							+ Math.Pow(TestModules.Items[h].X7 - CaseLibrary.Items[i].X7, 2) + Math.Pow(TestModules.Items[h].X8 - CaseLibrary.Items[i].X8, 2)
							+ Math.Pow(TestModules.Items[h].X9 - CaseLibrary.Items[i].X9, 2) + Math.Pow(TestModules.Items[h].X10 - CaseLibrary.Items[i].X10, 2);
					MinDistanceY[i] = CaseLibrary.Items[i].Y;
				}

				//Sort both arrays by the MinDistance
				Array.Sort(MinDistance, MinDistanceY);

				//Go through the CaseLibrary searching for nearer neighbors
				for (int i = NumOfNearestNeighbors; i < CaseLibrary.Items.Count; i++)
				{
					newDistance = Math.Pow(TestModules.Items[h].X1 - CaseLibrary.Items[i].X1, 2) + Math.Pow(TestModules.Items[h].X2 - CaseLibrary.Items[i].X2, 2)
							+ Math.Pow(TestModules.Items[h].X3 - CaseLibrary.Items[i].X3, 2) + Math.Pow(TestModules.Items[h].X4 - CaseLibrary.Items[i].X4, 2)
							+ Math.Pow(TestModules.Items[h].X5 - CaseLibrary.Items[i].X5, 2) + Math.Pow(TestModules.Items[h].X6 - CaseLibrary.Items[i].X6, 2)
							+ Math.Pow(TestModules.Items[h].X7 - CaseLibrary.Items[i].X7, 2) + Math.Pow(TestModules.Items[h].X8 - CaseLibrary.Items[i].X8, 2)
							+ Math.Pow(TestModules.Items[h].X9 - CaseLibrary.Items[i].X9, 2) + Math.Pow(TestModules.Items[h].X10 - CaseLibrary.Items[i].X10, 2);
					//If the Distance is smaller than the furtherest neighbor swap it in.
					if (newDistance < MinDistance[NumOfNearestNeighbors - 1])
					{
						//insert keeping the list sorted.
						MinDistance[NumOfNearestNeighbors - 1] = newDistance;
						MinDistanceY[NumOfNearestNeighbors - 1] = CaseLibrary.Items[i].Y;
						Array.Sort(MinDistance, MinDistanceY);
					}
				}

				//Add the Y values for the nearest neighbors to the h testmodule info.
				for (int i = 0; i < NumOfNearestNeighbors; i++)
				{
					TestModules.Items[h].NearestNeighbors.Add(MinDistanceY[i]);
				}
			}
		}

		public static void GeneratePredictions()
		{
			for (int i = 0; i < TestModules.Items.Count; i++)
			{
				TestModules.Items[i].PredictedNumberFaults = TestModules.Items[i].NearestNeighbors.Average();
			}

			DataEngine.PredictionsGenerated = true;

			EventHandler<EventArgs> myEvent = OnPredictionsGenerated;

			if (myEvent != null)
			{
				myEvent(null, new EventArgs());
			}
		}

		#endregion

		#region "Statistics Methods"

		public static void GenerateStatistics()
		{
			Double totalAbsoluteError = 0;
			Double totalRelativeError = 0;
			Double totalDeviation = 0;

			foreach (TestModule module in TestModules.ActualItems)
			{
				totalAbsoluteError += module.AbsoluteError;
				totalRelativeError += module.RelativeError;
				totalDeviation += Math.Pow(module.PredictedNumberFaults - module.Y, 2);
			}

			Statistics.AverageAbsoluteError = totalAbsoluteError / (Double)TestModules.ActualItems.Count;
			Statistics.AverageRelativeError = totalRelativeError / (Double)TestModules.ActualItems.Count;

			//Standard Deviation
			Statistics.StandardDeviation = Math.Sqrt(totalDeviation / TestModules.Items.Count);

			//Mean Squared Error
			Statistics.MeanSquaredError = totalDeviation / TestModules.Items.Count;

			DataEngine.StatisticsGenerated = true;

			EventHandler<EventArgs> myEvent = OnStatisticsGenerated;

			if (myEvent != null)
			{
				myEvent(null, new EventArgs());
			}
		}

		#endregion

		#region "Clear Methods"

		public static void ClearPredictions()
		{
			if (TestModules != null)
			{
				foreach (TestModule module in TestModules.Items)
				{
					module.PredictedNumberFaults = -1;
					module.NearestNeighbors.Clear();

					DataEngine.PredictionsGenerated = false;
				}
			}
		}

		public static void ClearStatistics()
		{
			if (TestModules != null)
			{
				foreach (TestModule module in TestModules.Items)
				{
					module.ResetErrors();

					Statistics.AverageAbsoluteError = 0;
					Statistics.AverageRelativeError = 0;
					Statistics.MeanSquaredError = 0;
					Statistics.StandardDeviation = 0;

					DataEngine.StatisticsGenerated = false;
				}
			}
		}

		#endregion
	}
}
